<?php
namespace Easy\Db\Driver;
use Easy\Db\DbDriver;
/**
 * PDO数据库驱动 
 */
class Pdo extends DbDriver{

    protected $PDOStatement = null;
    private   $table        = '';
    
    protected $_fetchMode = \PDO::FETCH_ASSOC;
    
    protected $_lastSql = '';
    
    protected $_lastBind = '';

    /**
     * 架构函数 读取数据库配置信息
     * @access public
     * @param array $config 数据库配置数组
     */
    public function __construct($config=''){
        if(!empty($config)) {
            $this->config   =   $config;
            if(empty($this->config['params'])) {
            	$this->config['params'] =   array();
            }
        }
    }

    /**
     * 连接分布式服务器
     * @access protected
     * @param boolean $master 主服务器
     * @return void
     */
    protected function multiConnect($master=false) {
    	if($master){
    		$host = $this->config['masterhost'];
    	}else{
    		$host = $this->config['slavehost'];
    	}
    	//$dsn = 'mysql:' . $this->config['database'] . 'host=' . $host . ';port='.$this->config['hostport'];
    	$dsn = 'mysql:dbname=' . $this->config['database'] . ';host=' . $host . ';port='.$this->config['hostport'];
    	$db_config = array(
    			'username'  =>  $this->config['username'],
    			'password'  =>  $this->config['password'],
    			'hostname'  =>  $master?$this->config['masterhost']:$this->config['slavehost'],
    			'hostport'  =>  $this->config['hostport'],
    			'database'  =>  $this->config['database'],
    			'params'    =>  isset($this->config['params'])?$this->config['params']:array(),
    			'charset'   =>  $this->config['charset'],
    			'dsn'		=>	$dsn
    			 
    	);
    	return $this->connect($db_config);
    }
    
    /**
     * 连接数据库方法
     * @access public
     */
    protected function connect($config='',$linkNum=0) {
    	if ( !$this->_linkID) {
    		if(empty($config))  $config =   $this->config;
    		if($this->pconnect) {
    			$config['params'][\PDO::ATTR_PERSISTENT] = true;//建立长链接
    		}else{
    			$config['params'][\PDO::ATTR_PERSISTENT] = false;
    		}
    		if(version_compare(PHP_VERSION,'5.3.6','<=')){ //禁用模拟预处理语句
    			$config['params'][\PDO::ATTR_EMULATE_PREPARES]  =   false;
    		}
    		$config['params'][\PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES '.$config['charset'];
    		try{
    			$this->linkID = new \PDO( $config['dsn'], $config['username'], $config['password'],$config['params']);
    		}catch (\PDOException $e) {
    			E($e->getMessage());
    		}
    		$this->linkID->exec('SET NAMES '.$config['charset']);
    		// 标记连接成功
    		$this->connected    =   true;
    	}
    	$this->_linkID=$this->linkID;
    	return $this->linkID;
    }
    
    public function insert($table, array $bind){
    	$this->multiConnect(true);
    	// extract and quote col names from the array keys
    	$cols = array();
    	$vals = array();
    	$i = 0;
    	foreach ($bind as $col => $val) {
    		$column = "`".$col."`";
    		$cols[] = $column;
    		$vals[] = ':'.$col;
    	}

    	// build the statement
    	$sql = "INSERT INTO `$table`"
    	.' (' . implode(', ', $cols) . ') '
    			. 'VALUES (' . implode(', ', $vals) . ')';
    	// execute the statement and return the number of affected rows
    	$stmt = $this->query($sql, $bind);
    	$result = $stmt->rowCount();
    	if($result !== false){
    		$lastInserId = $this->_linkID->lastInsertId();
    		if($lastInserId){
    			$result = $lastInserId;
    		}
    	}
    	return $result;
    }
    
    public function update($sql) {
    	$this->multiConnect(true);
    	/**
    	 * Execute the statement and return the number of affected rows
    		*/
    	$stmt = $this->query($sql,$this->bind);
    	$result = $stmt->rowCount();
    	return $result;
    }
    
    public function delete($sql){
    	$this->multiConnect(true);
    	$stmt = $this->query($sql,$this->bind);
    	$result = $stmt->rowCount();
    	return $result;
    }
    
    
    public function fetchAll($sql, $bind = array(), $fetchMode = null){
    	$this->multiConnect(false);
    	$bind = array_merge($bind,$this->bind);
    	if ($fetchMode === null) {
    		$fetchMode = $this->_fetchMode;
    	}
    	$stmt = $this->query($sql, $this->bind);
    	$result = $stmt->fetchAll($fetchMode);
    	return $result;
    }
    
    public function fetchRow($sql, $bind = array(), $fetchMode = null){
    	$this->multiConnect(false);
    	$bind = array_merge($bind,$this->bind);
    	if ($fetchMode === null) {
    		$fetchMode = $this->_fetchMode;
    	}
    	$stmt = $this->query($sql, $bind);
    	$result = $stmt->fetch($fetchMode);
    	return $result;
    }
    
    public function fetchCol($sql, $bind = array()){
    	$this->multiConnect(false);
    	$bind = array_merge($bind,$this->bind);
    	$stmt = $this->query($sql, $bind);
    	$result = $stmt->fetchAll(\PDO::FETCH_COLUMN, 0);
    	return $result;
    }
    
    public function fetchOne($sql, $bind = array()) {
    	$this->multiConnect(false);
    	$bind = array_merge($bind,$this->bind);
    	$stmt = $this->query($sql, $bind);
    	$result = $stmt->fetchColumn(0);
    	return $result;
    }
    
    public function fetchPairs($sql, $bind = array()){
    	$this->multiConnect(false);
    	$bind = array_merge($bind,$this->bind);
    	$stmt = $this->query($sql,$bind);
    	$data = array();
    	while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
            if(count($row) == 2){
                $v1 = array_shift($row);
                $v2 = array_shift($row);
                $data[$v1] = $v2;
            }else{
                $newRow = $row;
                $v1 = array_shift($row);
                $data[$v1] = $newRow;
            }
    	}
    	return $data;
    }
    
    /**
     * 直接执行sql语句，自动区分主从
     * @param unknown $sql
     * @param unknown $bind
     * @param string $fetchMode
     * @return unknown
     */
    public function execute($sql,$bind=array(),$fetchMode=null){
    	if(empty($sql)){
    		E('SQL_NOT_NULL');
    	}
    	$tmp_sql = strtolower(trim($sql));
    	if(strpos($tmp_sql,'select ') ===0){
    		$this->multiConnect(false);
    	}else{
    		$this->multiConnect(true);
    	}
    	$bind = array_merge($bind,$this->bind);
    	if ($fetchMode === null) {
    		$fetchMode = $this->_fetchMode;
    	}
    	$stmt = $this->query($sql, $bind);
    	$result = $stmt->fetchAll($fetchMode);
    	return $result;
    }
    
    public function getLastSql(){
    	$newArrayKeys = array();
    	foreach($this->_lastBind as $k=>$v){
    		if(strpos($k,':') === 0){
    			$newArrayKeys[$k] = '\''.$v.'\'';
    		}else{
    			$newArrayKeys[':'.$k] = '\''.$v.'\'';
    		}
    	}
    	$sql = str_replace(array_keys($newArrayKeys),array_values($newArrayKeys),$this->_lastSql);
    	return $sql;
    }
    /**
     * 释放查询结果
     * @access public
     */
    public function free() {
        $this->PDOStatement = null;
    }
    
    
    /**
     * 获取sql语句结果的查询语句
     * @param unknown $sql
     * @param unknown $bind
     * @return unknown
     */
    private function query($sql, $bind = array()){
    	// make sure $bind to an array;
    	if (!is_array($bind)) {
    		$bind = array($bind);
    	}
    	try{
    		$tmpsql = strtolower($sql);
    		$this->PDOStatement = $this->_linkID->prepare($sql);
    		$this->PDOStatement->execute($bind);
    		$errorInfo = $this->PDOStatement->errorInfo();
    		if($errorInfo[0] != '00000'){
    			throw new \PDOException($errorInfo[2], $errorInfo[1]);
    		}
    		// return the results embedded in the prepared statement object
    		$this->PDOStatement->setFetchMode(\PDO::FETCH_ASSOC);
    		$this->_lastSql = $sql;
    		$this->_lastBind = $bind;
    		$this->emptyBindParam();
    		return $this->PDOStatement;
    	}catch (\PDOException $e){
    		E($e->getMessage());
    		return false;
    	}
    }
    
    /**
     * 参数绑定
     * @access protected
     * @return void
     */
    private function bindPdoParam($bind){
    	// 参数绑定
    	foreach($bind as $key=>$val){
    		if(is_array($val)){
    			array_unshift($val,$key);
    		}else{
    			$val  = array($key,$val);
    		}
    		call_user_func_array(array(__NAMESPACE__.'\Pdo','bindParam'),$val);
    	}
    }
    
    /**
     * 清空绑定变量
     */
    private function emptyBindParam(){
    	$this->bind = array();
    }
    

    /**
     * 启动事务
     * @access public
     * @return void
     */
    public function startTrans() {
        $this->multiConnect(true);
        if ( !$this->_linkID ) return false;
        //数据rollback 支持
        if ($this->transTimes == 0) {
            $this->_linkID->beginTransaction();
        }
        $this->transTimes++;
        return ;
    }

    /**
     * 用于非自动提交状态下面的查询提交
     * @access public
     * @return boolen
     */
    public function commit() {
        if ($this->transTimes > 0) {
            $result = $this->_linkID->commit();
            $this->transTimes = 0;
            if(!$result){
                $this->error();
                return false;
            }
        }
        return true;
    }

    /**
     * 事务回滚
     * @access public
     * @return boolen
     */
    public function rollback() {
        if ($this->transTimes > 0) {
            $result = $this->_linkID->rollback();
            $this->transTimes = 0;
            if(!$result){
                $this->error();
                return false;
            }
        }
        return true;
    }

    /**
     * 关闭数据库
     * @access public
     */
    public function close() {
        $this->_linkID = null;
    }
}